using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Windows;

namespace SDF.Primitives
{
   /// <summary>
   /// Contains a collection of data objects.
   /// </summary>
   /// <typeparam name="T">The type of items in the collection.</typeparam>
    public class SmartDataObjectCollection<T> : IList, ICollection, ICollection<T>, IEnumerable<T>, IEnumerable, INotifyCollectionChanged,
        ISmartDataObjectCollection<T> where T: class, ISmartDataObject
   {
        #region Constructor

        public SmartDataObjectCollection()
        {
            InnerCollection = new List<T>();
        }

        #endregion

        #region Properties

        private List<T> InnerCollection { get; set; }

        public T this[int index]
        {
            get { return InnerCollection[index]; }
            set { SetItem(index, value); }
        }

        public int Count
        {
            get { return InnerCollection.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public bool IsSynchronized
        {
            get { return true; }
        }

        public object SyncRoot
        {
            get { return ((ICollection)InnerCollection).SyncRoot; }
        }

        public bool IsFixedSize
        {
            get { return false; }
        }

        #endregion

        #region Methods

        public void Add(T item)
        {
            this.InsertItem(this.Count, item);
        }

        public void Clear()
        {
            ClearItems();
        }

        protected virtual void ClearItems()
        {
            while (this.Count > 0)
                RemoveAt(0);
        }

        public bool Contains(T item)
        {
            return InnerCollection.Contains(item);
        }

        public void CopyTo(T[] array, int index)
        {
            InnerCollection.CopyTo(array, index);
        }

        public IEnumerator<T> GetEnumerator()
        {
            return InnerCollection.GetEnumerator();
        }

        public int IndexOf(T item)
        {
            return InnerCollection.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            InsertItem(index, item);
        }

        protected virtual void InsertItem(int index, T item)
        {
            if (InnerCollection.Contains(item))
                throw new ArgumentException("Cannot add the same element twice to the collection.");

            InnerCollection.Insert(index, item);
            OnChildAdded(item);
        }

        public bool Remove(T item)
        {
            int index = InnerCollection.IndexOf(item);
            if (index < 0)
                return false;
            RemoveItem(index);
            return true;
        }

        public void RemoveAt(int index)
        {
            RemoveItem(index);
        }

        protected virtual void RemoveItem(int index)
        {
            if ((index < 0) || (index >= this.Count))
                throw new ArgumentOutOfRangeException("index");

            T item = InnerCollection[index];
            InnerCollection.RemoveAt(index);
            OnChildRemoved(item);
        }

        protected virtual void SetItem(int index, T item)
        {
            if (index < this.Count)
                RemoveItem(index);
            InsertItem(index, item);
        }

        #endregion

        #region IList Members

        object IList.this[int index]
        {
            get { return this[index];} 
            set
            {
                var child = ValidateChild(value);
                this[index] = child;
            }
        }

        int IList.Add(object value)
        {
            var child = ValidateChild(value);
            this.Add(child);
            return this.Count;
        }

        bool IList.Contains(object value)
        {
            T item = value as T;
            if (item == null)
            {
                return false;
            }
            return this.Contains(item);
        }

        int IList.IndexOf(object value)
        {
            T item = value as T;
            if (item == null)
            {
                return -1;
            }
            return this.IndexOf(item);
        }

        void IList.Insert(int index, object value)
        {
            var child = ValidateChild(value);
            this.Insert(index, child);
        }

        void IList.Remove(object value)
        {
            T item = value as T;
            if (item != null)
            {
                this.Remove(item);
            }
        }

        #endregion

        #region ICollection Members

        void ICollection.CopyTo(Array array, int arrayIndex)
        {
            ((ICollection)InnerCollection).CopyTo(array, arrayIndex);
        }

        #endregion

        #region IEnumerator Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region ValidateChild Method

        protected T ValidateChild(object value)
        {
            if (value == null)
                throw new ArgumentNullException("value");

            T item = value as T;
            if (item != null)
                return item;

            item = Convert(value);
            if (item != null)
                return item;

            throw new ArgumentException(String.Format("Value '{0}' cannot be converted to a '{1}'.", value, typeof(T)));
        }

        protected virtual T Convert(object value)
        {
            return null;
        }

        #endregion

        #region Events

        public event NotifyCollectionChangedEventHandler CollectionChanged
        {
            add { _CollectionChanged += value; }
            remove { _CollectionChanged -= value; }
        }
        private NotifyCollectionChangedEventHandler _CollectionChanged;


        public event EventHandler<SmartDataObjectCollectionEventArgs<T>> ChildAdded
        {
            add { _ChildAdded += value; }
            remove { _ChildAdded -= value; }
        }
        private EventHandler<SmartDataObjectCollectionEventArgs<T>> _ChildAdded;

        public event EventHandler<SmartDataObjectCollectionEventArgs<T>> ChildRemoved
        {
            add { _ChildRemoved += value; }
            remove { _ChildRemoved -= value; }
        }
        private EventHandler<SmartDataObjectCollectionEventArgs<T>> _ChildRemoved;

        protected virtual void OnChildAdded(T child)
        {
            if (child != null)
                child.DataChanged += OnItemDataChanged; 
            
            var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, child);
            if (_CollectionChanged != null)
                _CollectionChanged(this, args);

            if (_ChildAdded != null)
                _ChildAdded(this, new SmartDataObjectCollectionEventArgs<T>(child));

            OnDataChanged(new DataChangedEventArgs(this, args));
        }

        protected virtual void OnChildRemoved(T child)
        {
            if (child != null)
                child.DataChanged -= OnItemDataChanged;

            var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, child);
            if (_CollectionChanged != null)
                _CollectionChanged(this, args);

            if (_ChildRemoved != null)
                _ChildRemoved(this, new SmartDataObjectCollectionEventArgs<T>(child));

            OnDataChanged(new DataChangedEventArgs(this, args));
        }

        /// <summary>
        /// Raised when the collection itself changes or when an item within the collection changes.
        /// </summary>
        public event EventHandler<DataChangedEventArgs> DataChanged
        {
            add { m_DataChanged += value; }
            remove { m_DataChanged -= value; }
        }
        private EventHandler<DataChangedEventArgs> m_DataChanged;

        private void OnDataChanged(DataChangedEventArgs e)
        {
            if (m_DataChanged != null)
                m_DataChanged(this, e);
        }

        protected virtual void OnItemDataChanged(object sender, DataChangedEventArgs e)
        {
            OnDataChanged(e);
        }

        #endregion
   }
}
